/** @file batch.cpp
	@brief Geometry batch */

#include "render/batch/batch.h"

using namespace G3;

//==============================================================================
// Global variables
//==============================================================================
double G3::Maximum_Batch_Depth = DEF_MAX_BATCH_DEPTH;

//==============================================================================
// Constructors
//==============================================================================
G3::BATCH::BATCH () {
    matID = -1;

    display_list_id = -1;

    indexStart = 0;
    numElements = 0;

    key = 0;

    last_render = 0;
}

G3::BATCH::BATCH (const BATCH &b) {
    BBox = b.BBox;

    matID = b.matID;

    display_list_id = b.display_list_id;

    indexStart = b.indexStart;
    numElements = b.numElements;

    key = b.key;

    last_render = b.last_render;
}

G3::BATCH::KEY::KEY () {
    fscr_layer = 0;
    vport = 0;
    vport_layer = 0;
    trans_type = 0;
    cmd = 0;
    mat_id = 0;
    pass = 0;
    depth = 0;
    sequence = 0;
    command = 0;
}

//==============================================================================
// Destructors
//==============================================================================
G3::BATCH::~BATCH () {
    if (display_list_id != -1)
        glDeleteLists (display_list_id, 1);
}

//==============================================================================
// Assignment operators
//==============================================================================
G3::BATCH::KEY& G3::BATCH::KEY::operator= (const G3::BATCH::KEY &k) {
    fscr_layer = k.fscr_layer;
    vport = k.vport;
    vport_layer = k.vport_layer;
    trans_type = k.trans_type;
    cmd = k.cmd;
    mat_id = k.mat_id;
    pass = k.pass;
    depth = k.depth;
    sequence = k.sequence;
    command = k.command;

    return *this;
}

G3::BATCH& G3::BATCH::operator= (const G3::BATCH &b) {
    BBox = b.BBox;
    matID = b.matID;
    display_list_id = b.display_list_id;
    indexStart = b.indexStart;
    numElements = b.numElements;
    key = b.key;
    last_render = b.last_render;

    return *this;
}

//==============================================================================
// Comparison operators
//==============================================================================
bool G3::BATCH::KEY::operator== (const G3::BATCH::KEY &k) {
    if (cmd && k.cmd) {
        return (fscr_layer == k.fscr_layer &&
                vport == k.vport &&
                vport_layer == k.vport_layer &&
                trans_type == k.trans_type &&
                sequence == k.sequence &&
                command == k.command);
    }
    return (fscr_layer == k.fscr_layer &&
            vport == k.vport &&
            vport_layer == k.vport_layer &&
            trans_type == k.trans_type &&
            cmd == k.cmd && mat_id == k.mat_id &&
            pass == k.pass && depth == k.depth);
}

//==============================================================================
// Dumps the key into the log
//==============================================================================
void G3::BATCH::KEY::Dump (G3::LOG *log) {
    if (log == NULL)
        return;

    log->Report ("Fullscreen layer: %d\n", fscr_layer);
    log->Report ("Viewport: %d\n", vport);
    log->Report ("Viewport layer: %d\n", vport_layer);
    log->Report ("Translucency type: %d\n", trans_type);
    log->Report ("Is command: %d\n", cmd);
    log->Report ("Material ID: %d\n", mat_id);
    log->Report ("Pass nr.: %d\n", pass);
    log->Report ("Depth: %d\n", depth);
    log->Report ("Sequence: %d\n", sequence);
    log->Report ("Command: %d\n", command);
}

//==============================================================================
// Packs the key structure into a long long key
//==============================================================================
long long G3::BATCH::packKey (const KEY &k) {
    long long result = 0;
    long long temp = 0;

#if SDL_BYTEORDER == SDL_LIL_ENDIAN
    if (k.cmd) {
        // 45 bits for command
        result |= k.command & 0x1FFFFFFFFFFFLL;
        // 8 bits for sequence number
        temp = k.sequence & 0xFF;
        result |= temp << 45;
    } else {
        // 24 bits for depth
        result |= k.depth & 0xFFFFFF;
        // 8 bits for rendering pass
        result |= k.pass << 24;
        // 21 bits for material id
        temp = k.mat_id & 0x1FFFFF;
        result |= temp << 32;
    }
    // 1 bit for command flag
    temp = k.cmd & 0x1;
    result |= temp << 53;
    // 2 bits for translucency type
    temp = k.trans_type & 0x3;
    result |= temp << 54;
    // 3 bits for viewport layer
    temp = k.vport_layer & 0x7;
    result |= temp << 56;
    // 3 bits for viewport
    temp = k.vport & 0x7;
    result |= temp << 59;
    // 2 bits for fullscreen layer
    temp = k.fscr_layer & 0x3;
    result |= temp << 62;
#else
    // 2 bits for fullscreen layer
    result |= k.fscr_layer & 0x3;
    // 3 bits for viewport
    temp = k.vport & 0x7;
    result |= temp << 2;
    // 3 bits for viewport layer
    temp = k.vport_layer & 0x7;
    result |= temp << 5;
    // 2 bits for translucency type
    temp = k.trans_type & 0x3;
    result |= temp << 8;
    // 1 bit for command flag
    temp = k.cmd & 0x1;
    result |= temp << 10;

    if (k.cmd) {
        // 8 bits for sequence number
        temp = k.sequence & 0xFF;
        result |= temp << 11;
        // 45 bits for command
        result |= (k.command & 0x1FFFFFFFFFFFLL) << 19;
    } else {
        // 21 bits for material id
        temp = k.mat_id & 0x1FFFFF;
        result |= temp << 11;
        // 8 bits for rendering pass
        temp = k.pass;
        result |= temp << 32;
        // 24 bits for depth
        temp = k.depth & 0xFFFFFF;
        result |= temp << 40;
    }
#endif

    key = result;

    return result;
}

//==============================================================================
// Unpacks the key structure from a long long key
//==============================================================================
G3::BATCH::KEY G3::BATCH::unpackKey (const long long &k) {
    KEY result;

    key = k;

#if SDL_BYTEORDER == SDL_LIL_ENDIAN
    // 1 bit for command flag
    result.cmd = (k & (0x1LL << 53)) >> 53;

    if (result.cmd) {
        // 45 bits for command
        result.command = k & 0x1FFFFFFFFFFFLL;
        // 8 bits for sequence number
        result.sequence = (k & (0xFFLL << 45)) >> 45;
    } else {
        // 24 bits for depth
        result.depth = k & 0xFFFFFF;
        // 8 bits for rendering pass
        result.pass = (k & (0xFFLL << 24)) >> 24;
        // 21 bits for material id
        result.mat_id = (k & (0x1FFFFFLL << 32)) >> 32;
    }

    // 2 bits for translucency type
    result.trans_type = (k & (0x3LL << 54)) >> 54;
    // 3 bits for viewport layer
    result.vport_layer = (k & (0x7LL << 56)) >> 56;
    // 3 bits for viewport
    result.vport = (k & (0x7LL << 59)) >> 59;
    // 2 bits for fullscreen layer
    result.fscr_layer = (k & (0x3LL << 62)) >> 62;
#else
    // 2 bits for fullscreen layer
    result.fscr_layer = k & 0x3;
    // 3 bits for viewport
    result.vport = (k & 0x1C) >> 2;
    // 3 bits for viewport layer
    result.vport_layer = (k & 0xE0) >> 5;
    // 2 bits for translucency type
    result.trans_type = (k & 0x300) >> 8;
    // 1 bit for command flag
    result.cmd = (k & 0x1) >> 10;

    if (result.cmd) {
        // 8 bits for sequence number
        result.sequence = (k & 0x7800LL) >> 11;
        // 45 bits for command
        result.command = (k & (0x1FFFFFFFFFFFLL << 19)) >> 19;
    } else {
        // 21 bits for material id
        result.mat_id = (k & (0x1FFFFFLL << 11)) >> 11;
        // 8 bits for rendering pass
        result.pass = (k & (0xFFLL << 32)) >> 32
        // 24 bits for depth
        result.depth = (k & (0xFFFFFFLL << 40)) >> 40;
    }
#endif

    return result;
}

//==============================================================================
// Generates a key for this batch
//==============================================================================
long long G3::BATCH::genKey (const G3::VEC3F &pos) {
    KEY k;

    // Batch material id
    k.mat_id = matID;

    // Batch depth approximation
    double dist = BBox.Distance (pos);

    if (dist < DEF_MAX_BATCH_DEPTH) {
        k.depth = (dist / DEF_MAX_BATCH_DEPTH) * 0xFFFFFF;
    } else
        k.depth = 0xFFFFFF;

    key = packKey (k);
    return key;
}

//==============================================================================
// Splits the key in 2 halves
//==============================================================================
void G3::BATCH::splitKey (long &msl, long &lsl) {
    lsl = (long) key;
    msl = (long) (key >> 32);
}

//==============================================================================
// Returns the key as a string
//==============================================================================
std::string G3::BATCH::keyToString () {
    char half1 [10], half2 [10];
    long l1 = 0, l2 = 0;

    splitKey (l1, l2);

    sprintf (half1, "%X", (uint) l1);
    sprintf (half2, "%X", (uint) l2);
    return std::string ("0x") + std::string (half1) + std::string (half2);
}

//==============================================================================
// Renders the batch
//==============================================================================
void G3::BATCH::render (G3::GEOM_POINTER *gp) {
    // Fix the render time
    last_render = SDL_GetTicks ();

    if (gp != NULL) {
        int id;

        glBegin (GL_TRIANGLES);

        for (int i=0; i<(int)numElements; i++) {
            id = indexStart / sizeof (int) + i;

            // Abort on invalid indexes
            if (id >= gp->num_indexes)
                break;
            else
                id = gp->indexes [id];

            if (gp->texcoords)
                glTexCoord2f (gp->texcoords [id].x, gp->texcoords [id].y);
            if (gp->normals)
                glNormal3f (gp->normals [id].x, gp->normals [id].y, gp->normals [id].z);
            if (gp->tangents)
                glTangent3fEXT (gp->tangents [id].x, gp->tangents [id].y, gp->tangents [id].z);

            glVertex3f (gp->vertices [id].x, gp->vertices [id].y, gp->vertices [id].z);
        }

        glEnd ();
    } else {
        if (display_list_id == -1) {
            /** \note For a VBO, the pointer starts at 0 */
            glDrawElements (GL_TRIANGLES, numElements, GL_UNSIGNED_INT, (void *) indexStart);
        } else {
            glListBase (0);
            glCallList (display_list_id);
        }
    }
}
